package cloud.daodao.storm.common.util.security;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author DaoDao
 */
public class Sm2Util {

    private static final String SM2_NAME = "sm2p256v1";

    public static final String SIGN_ID = "3633453170883686";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 获取 sm2 密钥对
     * BC 库使用的公钥 = 64 个字节 +1 个字节（04标志位），BC 库使用的私钥 = 32 个字节
     * SM2 秘钥的组成部分有 私钥 D 、公钥 X 、 公钥 Y , 他们都可以用长度为 64 的 16 进制的 HEX 串表示，
     * SM2 公钥并不是直接由 X + Y 表示 , 而是额外添加了一个头，当启用压缩时: 公钥 = 有头 + 公钥 X ，即省略了公钥 Y 的部分
     *
     * @return Map<String, String>
     */
    public static Map<String, String> keyPair() throws NoSuchAlgorithmException {
        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM2_NAME);
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //1.创建密钥生成器
        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
        //2.初始化生成器,带上随机数
        keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
        //3.生成密钥对
        AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
        ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic();
        ECPoint ecPoint = publicKeyParameters.getQ();
        // 把公钥放入map中,默认压缩公钥
        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
        String publicKey = Hex.toHexString(ecPoint.getEncoded(false));
        ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate();
        BigInteger intPrivateKey = privateKeyParameters.getD();
        // 把私钥放入map中
        String privateKey = intPrivateKey.toString(16);
        Map<String, String> map = new HashMap<>(1 << 4);
        map.put("publicKey", publicKey);
        map.put("privateKey", privateKey);
        return map;
    }


    public static String encrypt(String publicKey, String plains) throws InvalidCipherTextException, NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] cipher = encrypt(publicKey, plains.getBytes(StandardCharsets.UTF_8));
        return Hex.toHexString(cipher);
    }

    /**
     * 根据 publicKey 对原始数据 data，使用 SM2 加密
     *
     * @param publicKey PublicKey
     * @param data      byte[]
     * @return byte[]
     */
    public static byte[] encrypt(String publicKey, byte[] data) throws InvalidCipherTextException {
        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM2_NAME);
        // 构造ECC算法参数，曲线方程、椭圆曲线G点、大整数N
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
        return sm2Engine.processBlock(data, 0, data.length);
    }

    public static String decrypt(String privateKey, String cipher) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidCipherTextException {
        // 使用BC库加解密时密文以04开头，传入的密文前面没有04则补上
        if (!cipher.startsWith("04")) {
            cipher = "04" + cipher;
        }
        byte[] plains = decrypt(privateKey, Hex.decode(cipher));
        return new String(plains, StandardCharsets.UTF_8);
    }

    /**
     * 根据 privateKey 对加密数据 data，使用 SM2 解密
     *
     * @param privateKey PrivateKey
     * @param data       byte[]
     * @return byte[]
     */
    public static byte[] decrypt(String privateKey, byte[] data) throws InvalidCipherTextException {
        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM2_NAME);
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        BigInteger privateKeyD = new BigInteger(privateKey, 16);
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        sm2Engine.init(false, ecPrivateKeyParameters);
        return sm2Engine.processBlock(data, 0, data.length);
    }

    /**
     * 私钥签名
     *
     * @param privateKey 私钥
     * @param data       待签名内容
     * @return String
     */
    public static String sign(String privateKey, String data) throws CryptoException, NoSuchAlgorithmException {

        //待签名内容转为字节数组
        byte[] message = Hex.decode(data);

        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM2_NAME);
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());

        BigInteger privateKeyD = new BigInteger(privateKey, 16);
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);

        //创建签名实例
        SM2Signer sm2Signer = new SM2Signer();

        //初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678
        sm2Signer.init(true, new ParametersWithID(new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")), Strings.toByteArray(SIGN_ID)));
        sm2Signer.update(message, 0, message.length);
        //生成签名,签名分为两部分r和s,分别对应索引0和1的数组
        byte[] signBytes = sm2Signer.generateSignature();

        return Hex.toHexString(signBytes);
    }

    /**
     * 验证签名
     *
     * @param publicKey 公钥
     * @param data      待签名内容
     * @param sign      签名值
     * @return boolean
     */
    public static boolean verify(String publicKey, String data, String sign) {
        //待签名内容
        byte[] message = Hex.decode(data);
        byte[] signData = Hex.decode(sign);

        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM2_NAME);
        // 构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        //创建签名实例
        SM2Signer sm2Signer = new SM2Signer();
        ParametersWithID parametersWithID = new ParametersWithID(publicKeyParameters, Strings.toByteArray(SIGN_ID));
        sm2Signer.init(false, parametersWithID);
        sm2Signer.update(message, 0, message.length);
        //验证签名结果
        return sm2Signer.verifySignature(signData);
    }

}
